/*
 * @(#)NavigatorTag.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nhncorp.usf.web.taglibs.pager;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyTagSupport;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.nhncorp.lucy.security.xss.XssFilter;
import com.nhncorp.usf.core.config.PagerConfigInfo;
import com.nhncorp.usf.core.config.PagerConfigInfos;
import com.nhncorp.usf.core.result.template.directive.PagerInfo;

/**
 * Page Navigator {@link BodyTagSupport}.
 *
 * @author Web Platform Development Team
 */
public class NavigatorTag extends BodyTagSupport {
	static final long serialVersionUID = 5338212775576088828L;

	private static Log log = LogFactory.getLog(NavigatorTag.class);

	protected int page;

	protected int totalRows;

	protected int firstPage;

	protected int prevPage;

	protected int[] index;

	protected int nextPage;

	protected int lastPage;

	protected String pagerType = null;

	protected boolean auto;

	protected boolean appendTotalRows;

	protected String attributes = "";

	/**
	 * release
	 * */
	public void release() {
		super.release();
	}

	/**
	 * @throws JspException JspException
	 * @return int
	 * */
	public int doStartTag() throws JspException {

		HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();
		try {
			
			PagerInfo pagerInfo = (PagerInfo)request.getAttribute("pagerInfo");
			Integer totalRow = (Integer) request.getAttribute("totalRow");
			
			if (totalRow == null) {
				totalRow = 0;
			}
			
			if (pagerInfo == null) {
				throw new IllegalStateException("pager:navigater tag error - pagerInfo is not set");
			}
			
			pagerInfo.init(pagerType, totalRow, null, null);
			
			if (getRequestURI() == null) {
				if (pagerInfo.getRequestURI() == null) {
					throw new JspException("pager:navigater tag error - requestURI attribute must be specified.");
				}
				
				pageContext.setAttribute("requestURI", pagerInfo.getRequestURI());
				pageContext.setAttribute("queryString",
						checkQueryString(pagerInfo.getQueryString()));
			}

			if (pagerType == null) {
				pagerType = pagerInfo.getType();
			}

			
			page = pagerInfo.getPage();
			loadConfig(pagerInfo, request);
		} catch (NumberFormatException e) {
			throw new JspException(e.getMessage(), e);
		}

		if (auto) {
			try {
				doAllChildTag();
			} catch (IOException e) {
				log.warn(e);
			}
			return SKIP_PAGE;
		}
		
		return EVAL_BODY_INCLUDE;
	}

	/**
	 * @param queryString queryString
	 * @return queryString
	 * */
	private String checkQueryString(String queryString) {
		return XssFilter.getInstance().doFilter(queryString);
	}
	
	/**
	 * @param pagerInfo {@link PagerInfo}
	 * @param request {@link HttpServletRequest}
	 * */
	private void loadConfig(PagerInfo pagerInfo, HttpServletRequest request) {
		PagerConfigInfo pagerConfigInfo = PagerConfigInfos.get(pagerType);
		
		if (pagerConfigInfo == null) {
			throw new IllegalArgumentException("failed to find pager config for type '" + pagerType + "'");
		}
		
		totalRows = parseObject(pageContext.getAttribute("totalRows"), -1);
		
		if (totalRows == -1 && pagerInfo != null) {
			totalRows = pagerInfo.getTotalRows();
		}
		if (totalRows == -1) {
			totalRows = parseObject(request.getParameter("totalRows"), -1);
		}
		if (totalRows == -1) {
			totalRows = 0;
			log.warn("<d:navigator> totalRows attribute is missing or invalid.. " + getRequestURI());
		}

		int pageSize = parseObject(pageContext.getAttribute("pageSize"), 0);
		
		if (pageSize == 0 && pagerInfo != null) {
			pageSize = pagerInfo.getPageSize();
		}

		if (pageSize == 0) {
			pageSize = pagerConfigInfo.getPageSize();
		}

		int indexSize = parseObject(pageContext.getAttribute("indexSize"), 0);
		
		if (indexSize == 0 && pagerInfo != null) {
			indexSize = pagerInfo.getIndexSize();
		}
		
		if (indexSize == 0) {
			indexSize = pagerConfigInfo.getIndexSize();
		}

		firstPage = 1;
		lastPage = (totalRows - 1) / pageSize + 1;
		index = getIndexInfo(pagerInfo, indexSize);
		setNextPrev(pagerConfigInfo, indexSize);

	}

	/**
	 * @throws JspException JspException
	 * @throws IOException IOException
	 * */
	private void doAllChildTag() throws JspException, IOException {
		JspWriter writer = pageContext.getOut();
		FirstTag firstTag = new FirstTag();
		firstTag.setPageContext(pageContext);
		firstTag.setParent(this);
		firstTag.doStartTag();
		writer.println();
		PrevTag prevTag = new PrevTag();
		prevTag.setPageContext(pageContext);
		prevTag.setParent(this);
		prevTag.doStartTag();
		writer.println();
		IndexTag indexTag = new IndexTag();
		indexTag.setPageContext(pageContext);
		indexTag.setParent(this);
		indexTag.doStartTag();
		writer.println();
		NextTag nextTag = new NextTag();
		nextTag.setPageContext(pageContext);
		nextTag.setParent(this);
		nextTag.doStartTag();
		writer.println();
		LastTag lastTag = new LastTag();
		lastTag.setPageContext(pageContext);
		lastTag.setParent(this);
		lastTag.doStartTag();
	}

	/**
	 * @param obj obj
	 * @param defaultValue defaultValue
	 * @return intValue
	 * */
	protected int parseObject(Object obj, int defaultValue) {
		if (obj == null) {
			return defaultValue;
		}

		return Integer.parseInt(obj.toString());
	}

	public boolean isAuto() {
		return auto;
	}

	public void setAuto(boolean auto) {
		this.auto = auto;
	}

	public String getRequestURI() {
		return (String)pageContext.getAttribute("requestURI");
	}

	/**
	 * @param uri uri
	 * */
	public void setRequestURI(String uri) {
		pageContext.setAttribute("requestURI", ((HttpServletRequest)pageContext.getRequest()).getContextPath() + uri);
	}

	/**
	 * @param queryString queryString
	 * */
	public void setQueryString(String queryString) {
		pageContext.setAttribute("queryString", checkQueryString(queryString));
	}

	public String getQueryString() {
		return (String)pageContext.getAttribute("queryString");
	}

	public String getPagerType() {
		return pagerType;
	}

	public void setPagerType(String pagerType) {
		this.pagerType = pagerType;
	}

	public String getTotalRows() {
		return (String)pageContext.getAttribute("totalRows");
	}

	/**
	 * @param totalRows totalRows
	 * */
	public void setTotalRows(String totalRows) {
		pageContext.setAttribute("totalRows", totalRows);
	}

	public String getPageSize() {
		return (String)pageContext.getAttribute("pageSize");
	}

	/**
	 * @param pageSize pageSize
	 * */
	public void setPageSize(String pageSize) {
		pageContext.setAttribute("pageSize", pageSize);
	}

	public String getIndexSize() {
		return (String)pageContext.getAttribute("indexSize");
	}

	/**
	 * @param indexSize indexSize
	 * */
	public void setIndexSize(String indexSize) {
		pageContext.setAttribute("indexSize", indexSize);
	}

	public boolean isAppendTotalRows() {
		return appendTotalRows;
	}

	public void setAppendTotalRows(boolean appendTotalRows) {
		this.appendTotalRows = appendTotalRows;
	}

	public String getAttributes() {
		return attributes;
	}

	public void setAttributes(String attributes) {
		this.attributes = attributes;
	}

	/**
	 * @throws JspException JspException
	 * @return EVAL_PAGE
	 * */
	public int doEndTag() throws JspException {
		pagerType = null;
		return EVAL_PAGE;
	}

	/**
	 * 화면 상에 Item 출력 방식을 사용자가 정의할 수 있도록 한다. 선택한 페이지가 가운데 나오는 형식으로 구현하였다.
	 *
	 * @param pagerInfo pager에 대한 정보 정의 객체
	 * @param indexSize 화면에 출력할 index 크기
	 * @return 화면에 출력할 index 배열
	 */
	protected int[] getIndexInfo(PagerInfo pagerInfo, int indexSize) {
		int indexStart = page - indexSize / 2;
		int indexEnd = indexStart + indexSize - 1;
		
		if (indexStart <= 0) {
			indexStart = 1;
			indexEnd = indexStart + indexSize - 1;
			
			if (indexEnd > lastPage) {
				indexEnd = lastPage;
			}
		} else if (indexEnd > lastPage) {
			indexEnd = lastPage;
			indexStart = indexEnd - indexSize + 1;
			
			if (indexStart <= 0) {
				indexStart = 1;
			}
		}

		index = new int[indexEnd - indexStart + 1];
		
		for (int i = indexStart; i <= indexEnd; i++) {
			index[i - indexStart] = i;
		}

		return index;
	}

	/**
	 * Next와 Prev에 대한 Step 간격을 설정한다.
	 * <p>
	 * stepType이 index인 경우는 <web:index-size>에 설정한 값으로 Next, Prev가 상대적으로 아래와 같이 계산되어 설정된다.
	 * 단, Exception 처리를 생략한 공식이다.
	 * 		Prev = 현재 페이지 - indexSize
	 * 		Next = 현재 페이지 + indexSize
	 *
	 * stepType이 page인 경우는 Next는 다음 페이지, Prev는 이전 값으로 설정된다.
	 *
	 * @param pagerConfigInfo 환경 설정 접근 객체
	 * @param indexSize 화면에 출력할 index 크기
	 */
	protected void setNextPrev(PagerConfigInfo pagerConfigInfo, int indexSize) {
		if (pagerConfigInfo.getStepType().equals("index")) {
			prevPage = page - indexSize;
			
			if (prevPage < 1 && index[0] > 1) {
				prevPage = 1;
			}
			
			nextPage = page + indexSize;

			if (nextPage > lastPage && index[index.length - 1] < lastPage) {
				nextPage = lastPage;
			}
		} else {
			prevPage = page - 1;
			nextPage = page + 1;
		}
	}
}